package com.solarStruggle.util

import java.io.{File, InputStream}
import java.util.jar.JarFile
import scala.xml.{Node, XML}

/**
 * Class for parsing .ods (OpenOffice Calc document) used to store data used to control the game.
 * 
 * <p>Each worksheet is treated as a separate table.  The name of the worksheet is the name of the
 * table.  The first row defines a header row.  The values in this row are the keys for the value
 * in the corresponding columns of subsequent rows.  The exception is the first column, which is
 * treated special.  The first column of each row (other than the first) forms the id of that row.
 * </p>
 */
class ODS(filename: String) {

	/**
	 * Namespace for "table:" prefixed elements
	 */
	val tableNs = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"

	/**
	 * Helper function to extract an array of values from a row
	 */
	def getRowValues(row: Node): Seq[String] = {
		var cellValues: List[String] = Nil

		// Structure looks like
		// <table-cell>
		//   <p>text</p>
		// </table-cell>
		val cells = row \\ "table-cell"
		for(cell <- cells) {
			val cellP = cell \\ "p"
			// Some cells may not have any value; in this case, just use ""
			val cellVal = if(!cellP.isEmpty) cellP.first.text else ""

			// Some cells may have values repeated, usually the blank value
			cell.attribute(tableNs, "number-columns-repeated") match {
				case None => cellValues ::= cellVal
				case Some(x) => for(count <- 0 until x.text.toInt) { cellValues ::= cellVal }
			}
		}

		cellValues.reverse
	}

	/**
	 * Helper function to get an input stream for the XML content of an ODS file
	 */
	def getContentInputStream(file: File): InputStream = {
		val jarFile = new JarFile(file)
		val contentEntry = jarFile.getEntry("content.xml")
		jarFile.getInputStream(contentEntry)
	}

	val xml = XML.load(getContentInputStream(new File(filename)))

	var tables: List[ODSTable] = Nil

	// Loop through all the "table" elements.  These correspond to the ODS worksheets.  The name
	// of the table is the name of the file to which its entries will be written, as well as the
	// name of the base class from which all the classes in the file will extend.
	//
	// The first row of the table is a header.  The values are the names of the properties of the
	// objects.  Each subsequent row specifies a class whose properties are initialized by the
	// values in the row.
	//
	// The first column is special.  This column gives the name of the class to be created.
	for(table <- xml \\ "table") {
		table.attribute(tableNs, "name") match {
			case Some(tableName) =>

				var rows: List[ODSRow] = Nil

				val rowNodes = (table \\ "table-row")
				// Build a map from index to label -- take the first row, get a list of pairs of 
				// (value, index), reverse it to (index, value), and use this list to index a map
				val rowLabels = Map(getRowValues(rowNodes.first).toList.zipWithIndex.map { a => a match { case (a, b) => (b, a) } } : _*)

				for(row <- rowNodes.drop(1)) {

					var rowEntries = Map[String, String]()
					val rowValues = getRowValues(row)

					for(i <- 1 until rowValues.length) {
						if(rowLabels(i) != "") rowEntries += rowLabels(i) -> rowValues(i)
					}

					rows ::= ODSRow(rowValues(0), rowEntries)
				}

				rows = rows.reverse

				tables ::= ODSTable(tableName.toString, rows)
			case None =>
		}
	}
}

/**
 * A row in the table.  It consists of an id (the value of the first column) as well as a series
 * of name-value pairs (the value in subsequent columns keyed off the corresponding value in the
 * first row)
 */
case class ODSRow(id: String, values: Map[String, String])

/**
 * A table, which has a name (the name of the worksheet) and a list of entries
 */
case class ODSTable(name: String, rows: List[ODSRow])
